package warthog.scangen;

import java.io.*;
import java.util.*;

import warthog.cradle.*;

/**
 * The goal here is to convert a specification for a regular derivation language
 * (or rather a related set of them) into a scanner for that language.
 * These are converted into source for a new class which implements the scanner.
 * 
 * If a scanner action returns, the scanner may be called again and it will
 * pick up where it left off.
 * @author Ian Kjos
 *
 */
public class Bend extends Descent {
	private static Regex DOT = null; {
		CharClass cc = new CharClass(false);
		cc.addPoint(LF);
		cc.addPoint(CR);
		DOT = new Literal(cc);
	}
	private Map<String, Regex> regularDefinitions = new HashMap<String, Regex>();
	private List<StartState> currentStates = new ArrayList<StartState>();
	private JavaClass javaClass;
	public Bend(File path) throws IOException { super(new FileReader(path)); javaClass = new JavaClass(path); }
	
	String returnType = "void", thrown, defaultCode;
	private boolean strings = false; 
	
	public void bend() throws IOException {
		parseHeader();
		while (inWhite()||inEOL()) skipLine();
		match('%'); match('%'); matchEOL();
		parseRegularDefinitions();
		// Now we need at least one start state block.
		match('%');
		parseStartStates();
		while(!atEOF()) {
			if (inWhite() || inEOL()) skipLine();
			else if (maybe('%')) {
				if (maybe('%')) {
					matchEOL();  // Just saw a section separator. Stop trying
					break;       // to collect start states and rules.
				} else parseStartStates();
			}
			else parseRule();
		}
		// At this point, we have all bunch of rules and start states.
		// We need to get the DFSM nodes corresponding to each start state,
		StartState.determinize();
		Rule.checkReached();
		Color.minimize();
		
		System.err.println(javaClass.javaFile().toString());
		
		// Write the file...

		StringBuilder w = javaClass.code;
		StartState.writeStarts(w);
		Color.writeData(w);
		writeSkeleton();
		
		// And this is the part that supports the style of writing "user code"
		// after the second pair of %% (if it appears)
		while(!atEOF()) {
			javaClass.code(collectWholeLine().toString());
		}
		javaClass.writeFile();
	}
	private void parseHeader() throws IOException {
		while (true) {
			if (inWhite()||inEOL()) skipLine();
			else if (inAlpha()) {
				String optionName = parseWord("option");
				skipSpace();
				if ("package".equals(optionName)) {
					javaClass.packageName = collectLine().toString();
					matchEOL();
				}
				else if ("import".equals(optionName)) {
					javaClass.preamble.append("import ");
					javaClass.preamble.append(collectWholeLine());
				}
				else if ("return".equals(optionName)) {
					returnType = collectLine().toString();
					matchEOL();
				}
				else if ("throws".equals(optionName)) {
					thrown = collectLine().toString();
					matchEOL();
				}
				else if ("default".equals(optionName)) {
					defaultCode = parseAction();
					matchEOL();
				}
				else if ("abstract".equals(optionName)) {
					javaClass.isAbstract = true;
					matchEOL();
				}
				else if ("final".equals(optionName)) {
					javaClass.isFinal = true;
					matchEOL();
				}
				else if ("public".equals(optionName)) {
					javaClass.isPublic = true;
					matchEOL();
				}
				else if ("strings".equals(optionName)) {
					strings = true;
					matchEOL();
				}
				else if ("files".equals(optionName)) {
					strings = false;
					matchEOL();
				}
				else {
					error("option "+optionName+" not understood.");
				}
			}
			else break;
		}
	}
	private void writeSkeleton() {
		ScanStrategy strategy = strings?new StringStrategy():new FileStrategy();
		if (!strings) javaClass.preamble.append("import java.io.*;\n");
		StringBuilder w = javaClass.code;
		w.append(
				"  private int left, mark, right;\n"+
				"  public Start start;\n"+
				strategy.variables()+
				strategy.functions(javaClass.className)+
				"  public "+returnType+" scan() "+
				strategy.toss(thrown)
				);
		w.append(" {\n"+
				"    scan: while (true) {\n"+
				strategy.preToken()+
				"      int q = "+strategy.bol()+"?start.bol:start.any;\n"+
				"      right=left; int rule=0;\n"+
				"      token: while (q>=0) {\n"+
				"        if (accept[q]>0) { mark = right; rule=accept[q]; }\n"+
				strategy.boundsTest()+
				"        q = edges[q][find(bounds[q], "+strategy.charAt("right")+")];right++;\n"+
				"      }\n"+
				"      switch (rule) {\n"+
				"      case 0: /* Default Rule */ ");
		w.append(defaultCode!=null?defaultCode:"System.out.append("+strategy.charAt("mark")+");mark++;");
		w.append(" break;\n");
		Rule.writeActionCases(w); 
		w.append("      }\n"+
				"    }\n");
		w.append("    switch(start) {\n");
		StartState.writeEOFs(w);
		w.append("    }\n"+
				"  }\n"+
				"  private static int find(int[] is, int c) {\n"+
				"    int l=0, r=is.length;\n"+
				"    while (l<r) { int m=(l+r)/2; if (is[m]>c) r=m; else l=m+1; }\n"+
				"    return l-1;\n"+
				"  }\n"
				);
	}
	private void parseStartStates() throws IOException {
		currentStates.clear();
		skipSpace();
		while (inWord()) {
			currentStates.add(StartState.fetch(parseWord("State Name")));
			skipSpace();
		}
		matchEOL();
	}
	private void parseRegularDefinitions() throws IOException {
		for(;;) {
			if (inWord()) {
				String name = parseWord("identifier");
				skipSpace();
				regularDefinitions.put(name, parseRegex());
				matchEOL();
			} else if (inWhite()) skipLine();
			else if (is('%')) break;
			else if (inEOL()) matchEOL();
			else error("Expected regular definition, start state declaration, or comment");
		}
	}
	private void parseRule() throws IOException {
		int line = lineNr;
		if (is('<')) {
			// Expect an EOF rule
			match('<'); match('<');
			matchWord("EOF");
			match('>'); match('>');
			String action = parseAction();
			for (StartState q:currentStates) q.setEofCode(action); // NB: This is an example when a "once" construction would be nice.
		} else {
			boolean bol = maybe('^');
			Regex r = parseRegex();
			String action = parseAction();
			Rule rule = new Rule(line, r, action);
			for (StartState q:currentStates) q.addRule(rule, bol); // NB: Same here.
		}
		matchEOL();
	}
	private String parseAction() throws IOException {
		skipSpace();
		return (is('{') ? parseBraceBlock() : collectLine()).toString();
	}
	private StringBuilder parseBraceBlock() throws IOException {
		match('{');
		StringBuilder sb=new StringBuilder("{");
		while (true) switch (look) {
		case '}': sb.append((char)getch()); return sb;
		case '{': sb.append(parseBraceBlock()); break;
		case '"': sb.append(parseString()); break;
		case '\'': sb.append(parseCharConstant()); break;
		case EOF: error("EOF found amid action code.");
		case CR: case LF: skipEOL(); sb.append("\n"); break;
		default: sb.append((char)getch()); 
		}
	}
	private StringBuilder parseCharConstant() throws IOException {
		match('\'');
		StringBuilder sb=new StringBuilder("'");
		if (maybe('\\')) sb.append('\\');
		sb.append((char)getch());
		match('\'');
		sb.append('\'');
		return sb;
	}
	private StringBuilder parseString() throws IOException {
		match('"');
		StringBuilder sb=new StringBuilder("\"");
		while (true) switch (look) {
		case '"': sb.append((char)getch()); return sb;
		case '\\': sb.append((char)getch()); sb.append((char)getch()); break;
		case EOF: error("EOF found in string");
		case CR: case LF: error("Newline in string");
		default: sb.append((char)getch());
		}
	}
	private Regex parseRegex() throws IOException {
		Regex r = parseSequence();
		while (maybe('|')) r = new Alternative(r, parseSequence());
		return r;
	}
	private Regex parseSequence() throws IOException {
		Regex r = parseQuantified();
		while (inComponent()) r = new Sequence(r, parseQuantified());
		return r;
	}
	private Regex parseQuantified() throws IOException {
		Regex r = parseComponent();
		if (is('*')) { read(); return new Star(r); }
		if (is('+')) { read(); return new Plus(r); }
		if (is('?')) { read(); return new Hook(r); }
		return r;
	}
	private boolean inComponent() { return look>SP && look!=')' && look!='|'; }
	private Regex parseComponent() throws IOException {
		switch(look) {
		case '(': { read(); Regex r = parseRegex(); match(')'); return r; }
		case ')': case '|': { return new Nothing(); }
		case '[': {	read(); CharClass cc = parseClass(); match(']'); return new Literal(cc); }
		case '{': { read(); String name=parseWord("regular definition name"); match('}'); return getDefinition(name); }
		case '.': { read(); return DOT; }
		case '*': case '+': case '?': error("unclear quantifier");
		default: { return new Literal(parseLiteral()); }
		}
	}
	private int parseLiteral() throws IOException { return maybe('\\') ? parseEscape() : getch(); }
	private int parseEscape() throws IOException {
		if (is('U') || is('u')) { read(); return parseNibbles(4); }
		if (inHex()) return parseNibbles(2);
		if (maybe('s')) return SP;
		if (maybe('t')) return TAB;
		if (maybe('n')) return LF;
		if (maybe('r')) return CR;
		return getch();
	}
	private Regex getDefinition(String name) {
		Regex re = regularDefinitions.get(name);
		if (null==re) error("undefined regular definition "+name);
		return re;
	}
	private CharClass parseClass() throws IOException {
		CharClass cc = new CharClass(!maybe('^'));
		while (inClass()) {
			int a=parseLiteral();
			if (maybe('-')) {
				cc.addRange(a, parseLiteral());
			} else {
				cc.addPoint(a);
			}
		}
		return cc;
	}
	private boolean inClass() { return look>SP && !is(']'); }
	
}
